const EventEmitter = require("events");
const ModBusRTU = require("modbus-serial");
const net = require("net");

/**
 * Modbus 客户端类，用于与 PLC 设备进行通信
 * 继承自 EventEmitter，支持事件监听
 */
class ModbusClient extends EventEmitter {
  /**
   * 构造函数，初始化 Modbus 客户端
   * @param {string} ip - PLC 设备的 IP 地址
   * @param {number} port - PLC 设备的端口号
   */
  constructor(ip, port) {
    super();
    this.ip = ip;
    this.port = port;

    // 创建 Modbus RTU 客户端实例
    this.client = null;

    // 连接状态
    this.isConnected = false;

    // 重连相关配置
    this.reconnectInterval = null;
    this.reconnectAttempts = 0;

    // 最大重连次数和时间间隔
    this.MAX_RECONNECT_ATTEMPTS = 15;
    this.INITIAL_RECONNECT_DELAY = 3000;
    this.MAX_RECONNECT_DELAY = 30000;

    // 连接超时时间
    this.connectionTimeout = 5000;

    // 写入请求队列
    this.writeQueue = [];
    this.isProcessingQueue = false;

    // 初始化客户端
    this._initClient();
  }

  /**
   * 初始化客户端
   * @private
   */
  _initClient() {
    try {
      if (this.client) {
        // 清理旧客户端
        this._destroyClient();
      }

      this.client = new ModBusRTU();
      this._setupErrorHandlers();
    } catch (err) {
      log("error", `初始化Modbus客户端失败: ${err.message}`);
    }
  }

  /**
   * 销毁客户端
   * @private
   */
  _destroyClient() {
    try {
      if (this.client) {
        this.client.removeAllListeners();
        if (this.client.isOpen) {
          this.client.close();
        }
        this.client = null;
      }
    } catch (err) {
      // 忽略销毁错误
    }
  }

  /**
   * 设置错误处理程序
   * 监听 Modbus 客户端的错误、关闭和超时事件
   */
  _setupErrorHandlers() {
    if (!this.client) return;

    this.client.on("error", (err) => {
      log("error", `Modbus客户端错误: ${err.message}`);
      this._handleDisconnection();
    });

    this.client.on("close", () => {
      log("warn", "Modbus连接已关闭");
      this._handleDisconnection();
    });

    this.client.on("timeout", () => {
      log("warn", "Modbus连接超时");
      this._handleDisconnection();
    });
  }

  /**
   * 处理断开连接情况
   * @private
   */
  _handleDisconnection() {
    if (this.isConnected) {
      this.isConnected = false;
      this.emit("disconnect");
    }
    this._startReconnect();
  }

  /**
   * 连接 Modbus 服务器
   * @returns {Promise} 连接结果
   */
  async connect() {
    // 如果已经连接，直接返回
    if (this.isConnected && this.client && this.client.isOpen) {
      return true;
    }

    try {
      log("info", `尝试连接 Modbus TCP 服务器 ${this.ip}:${this.port}`);

      // 确保有有效的客户端
      if (!this.client) {
        this._initClient();
      }

      // 设置超时
      this.client.setTimeout(this.connectionTimeout);

      // 使用Promise包装连接过程，确保所有错误都被捕获
      const connected = await new Promise((resolve) => {
        let connectTimeoutId;

        // 设置连接超时
        connectTimeoutId = setTimeout(() => {
          log("error", `连接 ${this.ip}:${this.port} 超时`);
          try {
            this.client.close();
          } catch (err) {
            // 忽略错误
          }
          resolve(false);
        }, this.connectionTimeout);

        try {
          this.client.connectTCP(this.ip, { port: this.port }, (err) => {
            clearTimeout(connectTimeoutId);

            if (err) {
              log("error", `连接 ${this.ip}:${this.port} 失败: ${err.message}`);
              try {
                this.client.close();
              } catch (closeErr) {
                // 忽略错误
              }
              resolve(false);
            } else {
              this.isConnected = true;
              this.reconnectAttempts = 0;
              log("info", `成功连接 Modbus TCP 服务器 ${this.ip}:${this.port}`);
              this.emit("connect");
              resolve(true);
            }
          });
        } catch (err) {
          clearTimeout(connectTimeoutId);
          log("error", `连接过程中出现异常: ${err.message}`);
          resolve(false);
        }
      });

      if (connected) {
        // 连接成功后等待更长时间让PLC完全准备好
        log("info", "连接成功，等待PLC完全准备好...");
        await new Promise((resolve) => setTimeout(resolve, 3000));
      } else {
        this.isConnected = false;
      }

      return connected;
    } catch (err) {
      log("error", `连接失败: ${this.ip}:${this.port} - ${err.message}`);
      this.isConnected = false;
      return false;
    }
  }

  /**
   * 开始重连过程
   */
  _startReconnect() {
    // 清除之前的重连定时器
    if (this.reconnectInterval) {
      clearTimeout(this.reconnectInterval);
    }

    // 如果已连接则不需要重连
    if (this.isConnected && this.client && this.client.isOpen) {
      log("info", `已经连接到 ${this.ip}:${this.port}，无需重连`);
      this.reconnectAttempts = 0;
      return;
    }

    if (this.reconnectAttempts < this.MAX_RECONNECT_ATTEMPTS) {
      const delay = Math.min(
        this.INITIAL_RECONNECT_DELAY * Math.pow(1.5, this.reconnectAttempts),
        this.MAX_RECONNECT_DELAY
      );

      log(
        "warn",
        `[Reconnect] 尝试重连 in ${delay}ms (第 ${
          this.reconnectAttempts + 1
        } 次)`
      );

      this.reconnectInterval = setTimeout(async () => {
        try {
          // 重新初始化客户端
          this._initClient();

          const connected = await this.connect();
          if (!connected) {
            this.reconnectAttempts++;
            this._startReconnect();
          }
        } catch (err) {
          log("error", `重连过程出现异常: ${err.message}`);
          // 重新初始化客户端
          this._initClient();
          this.reconnectAttempts++;
          this._startReconnect();
        }
      }, delay);
    } else {
      log("error", `[Reconnect] 达到最大重连次数`);
      this.reconnectAttempts = 0;
      this.emit("maxReconnects");
    }
  }

  /**
   * 读取输入寄存器
   * @param {number} startAddress - 起始地址
   * @param {number} quantity - 读取数量
   * @returns {Promise<array>} 读取到的浮点数数组
   */
  async readInputRegisters(startAddress, quantity) {
    if (!this.client) {
      const error = new Error("Modbus client not initialized");
      log("error", error.message);
      throw error;
    }

    if (!this.isConnected || !this.client.isOpen) {
      const error = new Error("Client is not connected");
      log("error", error.message);
      this._handleDisconnection();
      throw error;
    }

    try {
      const data = await this.client.readInputRegisters(startAddress, quantity);
      const registers = data.data;
      const floatArray = [];

      for (let i = 0; i < registers.length; i += 2) {
        if (i + 1 < registers.length) {
          const buffer = Buffer.alloc(4);
          buffer.writeUint16BE(registers[i], 0);
          buffer.writeUint16BE(registers[i + 1], 2);
          const floatValue = Math.floor(buffer.readFloatLE(0) * 100) / 100;
          floatArray.push(floatValue);
        }
      }

      this.emit("inputRegistersRead", floatArray);
      return floatArray;
    } catch (err) {
      log(
        "error",
        `读取输入寄存器失败: ${err.message}; modbusCode=${err.modbusCode}`
      );

      // 处理连接相关错误
      if (this._isConnectionError(err)) {
        this._handleDisconnection();
      }

      throw err;
    }
  }

  /**
   * 读取保持寄存器
   * @param {number} startAddress - 起始地址
   * @param {number} quantity - 读取数量
   * @returns {Promise<array>} 读取到的寄存器数据
   */
  async readHoldingRegisters(startAddress, quantity) {
    if (!this.client) {
      const error = new Error("Modbus client not initialized");
      log("error", error.message);
      throw error;
    }

    if (!this.isConnected || !this.client.isOpen) {
      const error = new Error("Client is not connected");
      log("error", error.message);
      this._handleDisconnection();
      throw error;
    }

    try {
      const data = await this.client.readHoldingRegisters(
        startAddress,
        quantity
      );
      const registers = data.data;
      log("info", "读取保持寄存器成功", registers);
      this.emit("holdingRegistersRead", registers);
      return registers;
    } catch (err) {
      log(
        "error",
        `读取保持寄存器失败: ${err.message}; modbusCode=${err.modbusCode}`
      );

      // 处理连接相关错误
      if (this._isConnectionError(err)) {
        this._handleDisconnection();
      }

      throw err;
    }
  }

  /**
   * 判断是否为连接相关错误
   * @param {Error} err - 错误对象
   * @returns {boolean} 是否为连接相关错误
   */
  _isConnectionError(err) {
    const message = err.message || "";
    return (
      message.includes("Port Not Open") ||
      message.includes("Client is not connected") ||
      message.includes("ETIMEDOUT") ||
      message.includes("TCP Connection Timed Out") ||
      (err.code &&
        (err.code === "ECONNRESET" ||
          err.code === "ECONNREFUSED" ||
          err.code === "ENOTFOUND" ||
          err.code === "EHOSTUNREACH"))
    );
  }

  /**
   * 将写入请求添加到队列
   * @param {number} registerAddress - 寄存器地址
   * @param {array} registers - 要写入的寄存器值数组
   * @returns {Promise} 写入操作的结果
   */
  enqueueWriteRequest(registerAddress, registers) {
    return new Promise((resolve, reject) => {
      this.writeQueue.push({
        registerAddress,
        registers,
        resolve,
        reject,
      });

      this._processWriteQueue();
    });
  }

  /**
   * 处理写入队列
   */
  async _processWriteQueue() {
    if (this.isProcessingQueue || this.writeQueue.length === 0) {
      return;
    }

    this.isProcessingQueue = true;

    const mergedRequests = this._mergeWriteRequests(this.writeQueue);
    this.writeQueue = [];

    for (const {
      registerAddress,
      registers,
      resolve,
      reject,
    } of mergedRequests) {
      if (!this.client || !this.isConnected || !this.client.isOpen) {
        const error = new Error("Client is not connected");
        log("error", error.message);
        if (reject) {
          reject({
            error,
            registerAddress,
            registers,
          });
        }
        this._handleDisconnection();
        continue;
      }

      try {
        await this.client.writeRegisters(registerAddress, registers);
        this.emit("writeSuccess");
        if (resolve) {
          resolve({ registerAddress, registers });
        }
      } catch (err) {
        log(
          "error",
          `写入寄存器失败: ${err.message}; modbusCode=${err.modbusCode}`
        );

        // 处理连接相关错误
        if (this._isConnectionError(err)) {
          this._handleDisconnection();
        }

        this.emit("writeError", err);
        if (reject) {
          reject({ error: err, registerAddress, registers });
        }
      }
    }

    this.isProcessingQueue = false;

    // 如果队列中还有数据，继续处理
    if (this.writeQueue.length > 0) {
      setImmediate(() => this._processWriteQueue());
    }
  }

  /**
   * 合并写入请求
   */
  _mergeWriteRequests(requests) {
    const requestMap = new Map();
    for (const req of requests) {
      // 合并相同地址的请求，保留最新的
      requestMap.set(req.registerAddress, req);
    }
    return Array.from(requestMap.values());
  }

  /**
   * 销毁客户端连接
   */
  destroy() {
    // 清除所有定时器
    if (this.reconnectInterval) {
      clearTimeout(this.reconnectInterval);
      this.reconnectInterval = null;
    }

    // 销毁客户端
    this._destroyClient();

    this.isConnected = false;
    this.writeQueue = []; // 清空写入队列
    this.reconnectAttempts = 0;
    this.emit("destroy");
  }
}

function log(level, message, ...optionalParams) {
  const timestamp = new Date().toISOString();
  if (optionalParams.length > 0) {
    console[level](
      `[${timestamp}] [${level.toUpperCase()}] ${message}`,
      ...optionalParams
    );
  } else {
    console[level](`[${timestamp}] [${level.toUpperCase()}] ${message}`);
  }
}

module.exports = ModbusClient;
